
// Reference: http://audioprograming.wordpress.com/2012/10/29/lock-free-audio-io-with-opensl-es-on-android/

#include "_OpenSLESEngine.h"

#include <System/Diagnostics/Debug.h>
#include <System/Threading/Thread.h>
#include <System/String.h>
#include <System/Math.h>

using namespace Riccsson::System;
using namespace Riccsson::System::Diagnostics;
using namespace Riccsson::System::Threading;
using namespace Riccsson::Xna::Framework::Audio;

// For native audio.
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>

// pointer and size of the next player buffer to enqueue, and number of remaining buffers
short* nextBuffer;
unsigned nextSize;

// engine interfaces
static SLObjectItf engineObject = NULL;
static SLEngineItf engineEngine;

// output mix interfaces
static SLObjectItf outputMixObject = NULL;
static SLEnvironmentalReverbItf outputMixEnvironmentalReverb = NULL;

// buffer queue player interfaces
static SLObjectItf bqPlayerObject = NULL;
static SLPlayItf bqPlayerPlay;
static SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
static SLEffectSendItf bqPlayerEffectSend;

bool _bqPlayerCallback_complete = false;

// this callback handler is called every time a buffer finishes playing
void _bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void* context)
{
	_OpenSLESEngine* instance((_OpenSLESEngine*)context);

	Debug::WriteLine("_bqPlayerCallback calling.");
	
	if(instance->isReleased())
	{
		return;
	}

	// If no sound source is created. Skip to fetch sound buffers.
	if(instance->OnBuffer != null)
	{
		Debug::WriteLine("instance->OnBuffer calling.");
		instance->OnBuffer(nullptr, nullptr);
	}

	// If _OpenSLESEngine::submitBuffer(...) has not been called. (If no event is connected or game has not been finished with initializing.
	// Then we have to try again.
	if(!_bqPlayerCallback_complete)
	{
		Debug::WriteLine("instance->OnBuffer internally did not call _OpenSLESEngine::submitBuffer(...).");
		const int length = 1024;
		byte dummy[length];
		memset(&dummy, 0, sizeof(byte) * length);
		instance->submitBuffer(dummy, length);
	}

	// Submit done.
	_bqPlayerCallback_complete = false;
}

void _OpenSLESEngine::submitBuffer(byte* buffer, int length)
{
	Debug::WriteLine("submitBuffer calling.");

    // for streaming playback, replace this test by logic to find and fill the next buffer
    if (NULL != buffer && 0 != length)
	{
        SLresult result;
        // enqueue another buffer
        result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, buffer, length);
        // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT,
        // which for this code example would indicate a programming error

		if(result != SL_RESULT_SUCCESS)
		{
			Debug::WriteLine( string("submitBuffer failed: ") + result);
		}
		else
		{
			Debug::WriteLine("submitBuffer finish.");
		}
    }

	_bqPlayerCallback_complete = true;
}

_OpenSLESEngine::_OpenSLESEngine()
	: m_running(false)
	, m_threadRunning(false)
	, m_released(false)
{

}

void _OpenSLESEngine::initialize()
{
	SLresult result;

	// ========================= Create engine.
	Debug::WriteLine("== Create OpenSLES engine ==");

    // create engine
    result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
	Debug::WriteLine("slCreateEngine");

    // realize the engine
    result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
	Debug::WriteLine("Realize");

    // get the engine interface, which is needed in order to create other objects
    result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineEngine);
	Debug::WriteLine("GetInterface");

    // create output mix, with environmental reverb specified as a non-required interface
    const SLInterfaceID ids[1] = {SL_IID_ENVIRONMENTALREVERB};
    const SLboolean req[1] = {SL_BOOLEAN_FALSE};
    result = (*engineEngine)->CreateOutputMix(engineEngine, &outputMixObject, 1, ids, req);
	Debug::WriteLine("CreateOutputMix");

    // realize the output mix
    result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
	Debug::WriteLine("Realize");
	
	// ========================= Create Buffer Queue.

    // configure audio source
    SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2};
	SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 2, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT, SL_BYTEORDER_LITTLEENDIAN};
	//SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_44_1, SL_PCMSAMPLEFORMAT_FIXED_8, SL_PCMSAMPLEFORMAT_FIXED_8, SL_SPEAKER_FRONT_CENTER, SL_BYTEORDER_LITTLEENDIAN};
    SLDataSource audioSrc = {&loc_bufq, &format_pcm};
	Debug::WriteLine("loc_bufq");

    // configure audio sink
    SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
    SLDataSink audioSnk = {&loc_outmix, NULL};
	Debug::WriteLine("loc_outmix");

    // create audio player
    const SLInterfaceID ids_2[2] = {SL_IID_BUFFERQUEUE, SL_IID_EFFECTSEND};
    const SLboolean req_2[2] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
    result = (*engineEngine)->CreateAudioPlayer(engineEngine, &bqPlayerObject, &audioSrc, &audioSnk, 2, ids_2, req_2);
	Debug::WriteLine("CreateAudioPlayer");

    // realize the player
    result = (*bqPlayerObject)->Realize(bqPlayerObject, SL_BOOLEAN_FALSE);
	Debug::WriteLine("Realize");

    // get the play interface
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY, &bqPlayerPlay);
	Debug::WriteLine("GetInterface");

    // get the buffer queue interface
    result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE, &bqPlayerBufferQueue);
	Debug::WriteLine("GetInterface");

    // register callback on the buffer queue
    result = (*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue, _bqPlayerCallback, this);
	Debug::WriteLine("RegisterCallback");

	Debug::WriteLine("OpenSLES initializing finished.");

}

void _OpenSLESEngine::release()
{
	m_released = true;

	(*outputMixObject)->Destroy(outputMixObject);
    (*engineObject)->Destroy(engineObject);

	delete outputMixObject;
	delete engineObject;

	outputMixObject = null;
	engineObject = null;

	Debug::WriteLine("_OpenSLESEngine release()");
}

bool _OpenSLESEngine::isReleased()
{
	return m_released;
}

void _OpenSLESEngine::play()
{
    // set the player's state to playing
    SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
	Debug::WriteLine(string("_OpenSLESEngine playing: ") + result);

	_bqPlayerCallback(null, this);
	_bqPlayerCallback(null, this);
	
	Debug::WriteLine("_OpenSLESEngine play.");
}

void _OpenSLESEngine::stop()
{
    SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);
	Debug::WriteLine(string("_OpenSLESEngine stopped: ") + result);

	m_running = false;
}

void _OpenSLESEngine::pause()
{
    SLresult result = (*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PAUSED);
	Debug::WriteLine(string("_OpenSLESEngine paused: ") + result);
}